#include "stdafx.h"
#include "detail/bmock_func_info.hpp"
#include "detail/bmock_message.hpp"
#include <stdexcept>
#include <boost/algorithm/string/replace.hpp>

namespace bmock {
	func_info::func_info(std::string funcSig, std::string funcName)
	{
		parse(funcSig, funcName);	
	}

	void func_info::parse(std::string& funcSig, const std::string& funcName)
	{
		try
		{
			boost::replace_all(funcSig, "__cdecl ", "");
			boost::replace_all(funcSig, "__thiscall ", "");
			const size_t argsIndex = parseFuncName(funcSig, funcName);		
			removeExtraSpaces(funcName_);
			parseArgsAndPostfix(funcSig, argsIndex);
			parseReturnType(funcSig);
		} catch (std::out_of_range) {
			throw mock_dec_exception();
		}
	}

	size_t func_info::parseFuncName(std::string& funcSig, const std::string& funcName)
	{			
		size_t index = funcSig.find(funcName + "(");       
		if (index == std::string::npos)
			index = funcSig.find(funcName + "<");

		size_t counter = 0;
		while (index > 0 && funcSig[index-1] != ' ' && funcSig[index-1] != '&' && funcSig[index-1] != '*')
		{
			counter++;
			index--;
		}

		size_t endIndex = parseBrackets(funcSig, index+funcName.size()+counter, '<', '>');
		funcName_ = funcSig.substr(index, endIndex - index);	
		funcSig.replace(index, endIndex-index, "");			

		return index;

	}

	void func_info::parseArgsAndPostfix(std::string& funcSig, size_t index)
	{			
		size_t endIndex = parseBrackets(funcSig, index, '(', ')');
		std::string argsStr = funcSig.substr(index, endIndex - index);
		postFix_ = funcSig.substr(endIndex, funcSig.size() - endIndex);
		removeExtraSpaces(postFix_);
		funcSig.replace(index, funcSig.size() - index, "");			

		size_t argBegIndex = index = 1;
		while (argsStr.at(index) != ')')
		{
			switch (argsStr.at(index))
			{
			case '(' :
				index = parseBrackets(argsStr, index, '(', ')') - 1;
				break;
			case '<' :
				index = parseBrackets(argsStr, index, '<', '>') - 1;
				break;
			case ',' :
				std::string argToPush = argsStr.substr(argBegIndex, index-argBegIndex);
				removeExtraSpaces(argToPush);
				if (argToPush != "void")
					argTypes_.push_back(argToPush);
				argBegIndex = index+1;
				break;

			}
			index++;
		}
		if (argBegIndex != index)
		{
			std::string argToPush = argsStr.substr(argBegIndex, index-argBegIndex);
			removeExtraSpaces(argToPush);
			if (argToPush != "void" && argToPush != "")
				argTypes_.push_back(argToPush);
		}
	}

	void func_info::parseReturnType(std::string& funcSig)
	{
		returnType_ = funcSig;
		removeExtraSpaces(returnType_);
	}

	size_t func_info::parseBrackets(const std::string& str, size_t begIndex, const char begBracket, const char endBracket, int direction) const
	{
		if (str.at(begIndex) != begBracket)
			return begIndex;

		begIndex+=direction;
		int counter = 1;
		while (counter > 0)
		{
			if (str.at(begIndex) == begBracket)
				counter++;
			else if (str.at(begIndex) == endBracket)
				counter--;
			begIndex+=direction;
		}

		return begIndex;
	}

	void func_info::removeExtraSpaces(std::string& str)
	{
		int i, counter=0;

		for (i = 0; i < (int)str.size() && str.at(i) == ' '; i++);
		str.replace(0, i, "");

		for (i = (int)str.size() - 1; i >= 0 && str.at(i) == ' '; i--, counter++ );
		str.replace(i+1,counter,"");
	}

	bool func_info::operator== (const func_info& other) const
	{
		if (funcName_ != other.funcName_ || returnType_ != other.returnType_)
			return false;

		if (argTypes_.size() != other.argTypes_.size())
			return false;

		for (size_t i = 0; i < argTypes_.size(); i++)
		{
			const std::string& curr = argTypes_.at(i);
			const std::string& otherCurr = other.argTypes_.at(i);

			if ( curr != otherCurr)
				return false;		
		}
		return true;
	}

	std::string func_info::get_func_sig() const
	{
		std::string ret = "";

		ret += get_return_type();
		ret += " ";

		ret += get_func_name();
		ret += "(";

		for (size_t i = 0; i < argTypes_.size(); i++)
		{
			ret += argTypes_[i];
			if ( i < argTypes_.size() - 1 )
				ret += ",";
		}

		ret += ")";
		if (postFix_ != "")
		{
			ret += " ";
			ret += postFix_;
		}

		return ret;
	}
}